package common;


import ap.BDDACLWrapper;
import common.apkeep.PPM;
import common.apkeep.Trie;
import common.correspondence.BDDIOTransfer;
import common.correspondence.CountWrap;
import common.correspondence.Message;
import common.proto.out.CountMessage;
import jdd.bdd.BDD;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;


public class Device extends Thread{
    public static String CONFIG_DIR_PATH = "config/";
    public static String LOG_OUTPUT_DIR_PATH = "time/";

    String configFilePath;
    String requirementFilePath;
    boolean already;

    public String name;
    Map<Requirement, Node[]> nodes;
    public Map<Integer, Requirement> requirementMap;
    public Vector<FIB> fibs;
    List<Rule> rules;
    Map<Rule, Rule> toRule;
    Rule defaultRule;
    Trie trie;
    PPM ppm;
    LinkedList<Integer> transferredPreds; // init when update
    boolean logRecord;

    public String ip;
    public HashMap<String, PortInterface> ports;
    public HashMap<String, String> deviceTable;

    protected LogRecorder lr;
    public String timeFilePath;

    InputStreamReader isr;
    BufferedReader br;
    public BDDACLWrapper bddEngine;
    public final BDDIOTransfer bt;
    public boolean output;

    public Device(String configName, String reqConfigName, String fwdConfigName){
        this(configName, reqConfigName, fwdConfigName, true);
    }

    public Device(String configName, String reqConfigName, String fwdConfigName, boolean output){
        this();
        this.output = output;
        this.configFilePath = configName;
        this.requirementFilePath = reqConfigName;
        long t1 = System.nanoTime();
        readReqFile(reqConfigName);
        long t2 = System.nanoTime();

        printf("read requirement file time:%.5fms\n", (t2-t1)/1000000.0);

        readConfigFile(configName);
        long t3 = System.nanoTime();
        printf("read config file time:%.5fms\n", (t3-t2)/1000000.0);
        long t4 = System.nanoTime();
        if(fwdConfigName != null){
            readForwardFile(fwdConfigName);
            t4 = System.nanoTime();
            printf("read fwd file time:%.5fms\n", (t4-t3)/1000000.0);
        }

        long t5 = System.nanoTime();
        printf("sort fibs time:%.5fms\n", (t5-t4)/1000000.0);

        for(Node[] ns: nodes.values())
            if(ns!=null)
                for(Node node: ns){
                    node.start();
                }


        long t6 = System.nanoTime();
        printf("run node thread time:%.5fms\n", (t6-t5)/1000000.0);
        printf("%s start\n", name);

        timeFilePath = LOG_OUTPUT_DIR_PATH + "time.txt";
        File file = new File(LOG_OUTPUT_DIR_PATH);
        file.mkdirs();
        lr = new LogRecorder(LOG_OUTPUT_DIR_PATH + name + "_time.csv");

        already = true;
//        try{
//            System.gc();
//            Runtime runtime=Runtime.getRuntime();//        addRecord(id, index, "cib num", cibOut.announcements.size());
//            println(fibs.size());
//            println("内存用量："+(runtime.totalMemory()-runtime.freeMemory())  + " B");
//        }catch(Exception e){
//            e.printStackTrace();
//        }

    }

    protected Device(){
        super("cmd");
        already = false;
        ports = new HashMap<>();
        deviceTable = new HashMap<>();
        fibs = new Vector<>();
        bddEngine = new BDDACLWrapper();
        nodes = new HashMap<>();
        requirementMap = new HashMap<>();

        bt = new BDDIOTransfer(bddEngine.getBDD());
        rules = new ArrayList<>();
        trie = new Trie();
        logRecord = false;
        ppm = new PPM(bddEngine.getBDD());
        transferredPreds = new LinkedList<>();
        toRule = new HashMap<>();
        defaultRule = null;
    }
    void checkLec(){
        int u = 0;
        boolean flag = true;
        BDD bdd = bddEngine.getBDD();
        for(int i=0;i<fibs.size();i++){
            u = bdd.orTo(u, fibs.get(i).predicate);
            for(int j=i+1;j<fibs.size();j++){
                if(bdd.and(fibs.get(i).predicate, fibs.get(j).predicate) != 0){
                    System.out.printf("%s and %s is not zero.\n", i, j);
                    flag = false;
                }
            }
        }
        if(u!= 1){
            System.out.println("union is not one.");
            flag = false;
        }
        if(flag) System.out.println("right!");
    }
    public void printf(String format, Object ... args){
        if(output)
            System.out.printf(format, args);
    }

    public void println(Object o){
        if(output)
            System.out.println(o);
    }

    public void println(){
        if(output)
            System.out.println();
    }

    private void sortFibs(){
//        long t1 = System.nanoTime();
//        fibs.add(new FIB(FIB.ForwardType.ALL, new HashSet<>(), 0, 0));
//        FIB.SortAndSetPredicate(fibs, bddEngine);
//        long t2 = System.nanoTime();
//        mergeFibs();
//        long t3 = System.nanoTime();
//        printf("sort and set:%.4fms, merge:%.4fms\n", (t2-t1)/1000000.0, (t3-t2)/1000000.0);
        BDD bdd = bddEngine.getBDD();
        Map<String, Integer> fwdBdd = new HashMap<>();
        Map<String, Integer> fibIndex = new HashMap<>();
        int index = 0;
        for(Rule rule:rules){
            synchronized (bdd) {
                if (fwdBdd.containsKey(rule.forward)) {
                    int oldBdd = fwdBdd.get(rule.forward);
                    int newBdd = bdd.orTo(oldBdd, rule.hit);
                    fwdBdd.put(rule.forward, newBdd);
                } else {
                    fwdBdd.put(rule.forward, bdd.ref(rule.hit));
                    fibIndex.put(rule.forward, index++);
                }
            }
            rule.lecIndex = fibIndex.get(rule.forward);
        }
        fibs.clear();
        for(String ignored :fibIndex.keySet()){
            fibs.add(null);
        }
        for(String to:fwdBdd.keySet()){
            int predicate = fwdBdd.get(to);
            fibs.set(fibIndex.get(to), new FIB(predicate, to));
        }

    }
    private int getFibKey(FIB fib){
        int i=0;
        for(String s:fib.forward){
            i += s.hashCode();
        }
        return i+fib.type.hashCode();
    }
    private void mergeFibs(){
        Map<Integer, List<FIB>> fibSet = new HashMap<>();
        for(FIB f:fibs){
            int key = getFibKey(f);
            if(!fibSet.containsKey(key)){
                fibSet.put(key, new ArrayList<>());
            }
            fibSet.get(key).add(f);
        }
        BDD bdd = bddEngine.getBDD();
        List<FIB> removeFib = new LinkedList<>();
        for(List<FIB> fl:fibSet.values()){
            if(fl.size() == 1) continue;
            FIB f = fl.get(0);
            for(int i=1;i<fl.size();i++){
                FIB needMerge = fl.get(i);
                synchronized (bdd){
                    f.predicate = bdd.orTo(f.predicate, needMerge.predicate);
                    bdd.deref(needMerge.predicate);
                }
                removeFib.add(needMerge);
            }
        }
        fibs.removeAll(removeFib);
    }

    protected void readForwardFile(String configName){
        if(!fileOpen(configName))return;
        fibs.clear();
        clearRule();
        String line;
        while ((line = readFileLine()) != null) {
            String[] token = line.split("\\s+", 4);
            if(token[0].equals("fw")){
                FIB.ForwardType ft = token[3].equals("self")? FIB.ForwardType.DROP: FIB.ForwardType.ALL;
                String to = token[3].split("\\.", 2)[0];
                long ip = Long.parseLong(token[1]);
                int prefix = Integer.parseInt(token[2]);
//                fibs.add(new FIB(ft, to, ip, prefix));
                Rule rule = new Rule(ip, prefix, to);
                insertRule(rule);
            }
        }
        if(defaultRule == null) {
            Rule defaultRule = new Rule(0, 0, rules.iterator().next().forward);

            insertRule(defaultRule);
        }
        sortFibs();
    }

    private void readForwardFileRuntime(String configName){
        if(!fileOpen(configName))
            return;

        Vector<FIB> oldFibs = new Vector<>(fibs);
        readForwardFile(configName);

        String id = Utility.getRandomString();
        updateFibForNode(oldFibs);
        BDD bdd = bddEngine.getBDD();
        for(FIB fib:oldFibs){
            bdd.deref(fib.predicate);
        }
    }

    private void clearRule(){
        BDD bdd = bddEngine.getBDD();
        for(Rule rule:rules){
            bdd.deref(rule.hit);
            bdd.deref(rule.match);
        }
        rules.clear();
    }

    protected void readConfigFile(String configName){
        if(!fileOpen(configName))System.exit(0);

        String line = readFileLine();
        String[] data = split(line, 2);

        name = data[0];
        ip = data[1];

        while ((line = readFileLine()) != null) { //读取出来的数据存放在数组str中
            data = split(line, 2);
            switch (data[0]){
                case "link":{
                    data = split(data[1],4);
                    if(data[0].equals("virtual")) break;
                    addLink(data[0], data[1], data[2], data[3]);
                    break;
                }
                case "req":{
                    data = split(data[1], 2);
                    initReqNodes(Integer.parseInt(data[0]), Integer.parseInt(data[1]));
                    break;
                }
                case "flag":{
                    data = split(data[1], 5);
                    setNodeNeighbor(Integer.parseInt(data[0]), Integer.parseInt(data[1]), data[2], data[3],
                            Integer.parseInt(data[4]));
                    break;
                }
                case "fwd":{
                    data = split(data[1], 4);
                    FIB.ForwardType _t = data[2].equals("ANY")? FIB.ForwardType.ANY: FIB.ForwardType.ALL;
                    fibs.add(new FIB(_t, data[3].split("\\s+"), Long.parseLong(data[0]), Integer.parseInt(data[1])));
                    break;
                }
            }
        }
        fileClose();
    }

    protected void readReqFile(String configName){
        String line;
        if (!fileOpen(configName)) System.exit(0);
        while ((line = readFileLine()) != null){
            Requirement r = new Requirement(line, this);
            if(!r.ok){
                System.err.println("The requirement file format is incorrect.");
            }
            nodes.put(r, null);
            requirementMap.put(r.index, r);
        }
        fileClose();
    }

    protected Node[] getNodeList(int reqIndex){
        return nodes.get(requirementMap.get(reqIndex));
    }

    private void initReqNodes(int reqIndex, int nodeNum){
        Requirement r = requirementMap.get(reqIndex);
        if(r != null){
            nodes.put(r, new Node[nodeNum]);
        }
    }

    protected void addLink(String name, String ip, String recPort, String sendToPort){
        PortInterface p;
        deviceTable.put(name, ip);
        p = new Port(name, recPort, sendToPort, ip, this);
        ports.put(name, p);
    }

    private void setNodeNeighbor(int reqIndex, int nodeIndex, String flag, String neighborName, int neighborNodeIndex){
        Requirement r = requirementMap.get(reqIndex);
        Node node = nodes.get(r)[nodeIndex];
        if(node == null){
            node = new Node(this, nodeIndex,r);
        }

        NodePointer np = new NodePointer(neighborName, neighborNodeIndex, reqIndex);
        if (flag.equals("next")) node.addNext(np);
        else if (flag.equals("prev")) node.addPrev(np);
        nodes.get(r)[nodeIndex] = node;
    }

    public void receive(Message msg){
        if(msg.type == Message.MessageType.Message)
            printf("收到%s消息：%s\n", msg.source, msg.dataMap);
        else if (msg.type == Message.MessageType.Count){
            long t1 = System.nanoTime();
            CountMessage.CountResult c = CountWrap.messageToCountResult(msg.dataBytes);
//            CountMessage.CountResult c = (CountMessage.CountResult) msg.message;
            long t2 = System.nanoTime();
            receiveCount(c, msg.source, msg.receiveTime);
            addRecord(c.getId(), c.getDestinationNodeIndex(), "* msg size", msg.dataBytes.length);
        }
    }

    public void receiveCount(CountMessage.CountResult res, String src, long time) {
        long t1 = System.nanoTime();
        CIBIO cibio = new CIBIO();
        cibio.withdraw = res.getCib().getWithdrawList();
        cibio.announcements = new Vector<>();
        cibio.packetSpace = res.getPacketSpace();
        CountMessage.Cib cib = res.getCib();
        for(CountMessage.Announcement an: cib.getAnnouncementsList()){
            cibio.announcements.add(new Announcement(an.getId(), an.getPredicate(), new Vector<>(an.getCountList())));
        }
        NodePointer np = new NodePointer(src, res.getSourceNodeIndex(), res.getRequirementIndex());
        String id =res.getId();
//        System.out.println(res.getNodeTreeList().size());
        long t2, t3;
        t2 = System.nanoTime();
        bt.cibIOPredicateParse(res.getNodeTreeList(), cibio);
        t3 = System.nanoTime();

//        if(res.getDebug()){
//            addRecord(id, res.getDestinationNodeIndex(), "count load after merge", t2-t1);
//        }else{
        getNodeList(res.getRequirementIndex())[res.getDestinationNodeIndex()].receiveCount(cibio, np, id);
        addRecord(id, res.getDestinationNodeIndex(), "*count receive", time);
//        addRecord(id, res.getDestinationNodeIndex(), "count load", t2-t1);
//        addRecord(id, res.getDestinationNodeIndex(), "bdd load", t3-t2);
//        }

    }


    public void receiveCont(CIBIO cibIn, NodePointer from, NodePointer to, String id){
        getNodeList(to.reqIndex)[to.index].receiveCount(cibIn, from, id);
    }

    public void send(String dst, Message msg){
        PortInterface p = ports.get(dst);

        if(p == null)
            printf("%s不在转发表中\n", dst);
        else
            p.send(msg);

    }

    public void sendCount(Iterable<NodePointer> nps, int index, CIBIO cibOut, String id){
        long t1 = System.nanoTime();
        List<Integer> bddList = new LinkedList<>();
        for(Announcement an:cibOut.announcements){
            bddList.add(an.predicate);
        }
        bddList.add(cibOut.packetSpace);

        CountMessage.Cib.Builder cibBuilder = CountMessage.Cib.newBuilder();
        cibBuilder.addAllWithdraw(cibOut.withdraw);
        for(Announcement an:cibOut.announcements){
            CountMessage.Announcement.Builder aBuilder = CountMessage.Announcement.newBuilder();
            aBuilder.addAllCount(an.count);
            aBuilder.setPredicate(an.predicate);
            aBuilder.setId(an.id);
            cibBuilder.addAnnouncements(aBuilder.build());
        }
        CountMessage.CountResult.Builder builder = CountMessage.CountResult.newBuilder();
        builder.setId(id);
        builder.setSourceNodeIndex(index);
        builder.setCib(cibBuilder.build());
        long tt1 = System.nanoTime();
        bt.bddsToArray(bddList, builder);
        long tt2 = System.nanoTime();

//        builder.addAllNodeTree(bddNodeTree);
        builder.setDebug(false);
        long t2 = System.nanoTime();
        for(NodePointer np:nps){
            builder.setRequirementIndex(np.reqIndex);
            builder.setDestinationNodeIndex(np.index);

            send(np.name, new Message(Message.MessageType.Count.ordinal(), builder.build()));
        }
        long t3 = System.nanoTime();
//        addRecord(id, index, "cib num", cibOut.announcements.size());
//        addRecord(id, index, "*bdd save map size", builder.getNodeTreeCount());
//        addRecord(id, index, "*bdd save % map size", (tt2-tt1)*1.0/builder.getNodeTreeCount());

//        addRecord(id, index, "count save", t2-t1);
//        addRecord(id, index, "serialization", t3-t2);
    }

    void findRuleToLecIndex(Rule rule){
        for(int i=0;i<fibs.size();i++){
            FIB fib = fibs.get(i);
            if (fib.forward.contains(rule.forward)){
                rule.lecIndex = i;
            }
        }
    }

    void changeLecTable(List<Change> changes){
        BDD bdd = bddEngine.getBDD();
        for (Change change:changes){
            boolean oldChange = false, newChange = false;
            for(FIB fib:fibs){
                if(fib.forward.contains(change.newPort)){
                    synchronized (bdd){
                        fib.predicate = bdd.orTo(fib.predicate, change.predicate);
                    }
                    newChange = true;
                }else if(fib.forward.contains(change.oldPort)){
                    synchronized (bdd){

                        int tmp = bdd.deref(bdd.not(change.predicate));
                        fib.predicate = bdd.andTo(fib.predicate, tmp);
                        bdd.deref(tmp);
                    }
                    oldChange = true;
                }
                if(oldChange && newChange)break;
            }
            if(!newChange){
                fibs.add(new FIB(change.predicate, change.newPort));
            }
        }
    }

    public void modifyRule(long ipAddr, int prefix, String oldTo, String newTo){
        oldTo = oldTo.split("\\.")[0];
        newTo = newTo.split("\\.")[0];
        modifyRule(new Rule(ipAddr, prefix, oldTo), new Rule(ipAddr, prefix, newTo));
    }

    public void modifyRule(Rule oldRule, Rule newRule){
        long ts = System.nanoTime();

        if(!rules.contains(oldRule)) {
            println("rule is not exists.");
            return;
        }
        List<Change> changes1 = identifyChangesDelete(oldRule);
        List<Change> changes2 = identifyChangesInsert(newRule);
        changes2.addAll(changes1);
        changeLecTable(changes2);
        getChangeToUpdateCIB(changes2, ts);
    }

    public void deleteRule(long ipAddr, int prefix, String to){
        to = to.split("\\.")[0];
        deleteRule(new Rule(ipAddr, prefix, to));
    }

    public void deleteRule(Rule rule){
        long ts = System.nanoTime();

        if(!rules.contains(rule)) {
            println("rule is not exists.");
            return;
        }

        rule = toRule.get(rule);
        List<Change> changes = identifyChangesDelete(rule);
        println(changes);
        changeLecTable(changes);
        getChangeToUpdateCIB(changes, ts);
    }

    public void insertRule(long ipAddr, int prefix, String to){
        to = to.split("\\.")[0];
        insertRule(new Rule(ipAddr, prefix, to));
    }

    public void insertRule(Rule rule) {
        long ts = System.nanoTime();
        rules.add(rule);

        if(rule.ip == 0 && rule.prefixLen == 0) defaultRule = rule;

        rule.match = bddEngine.encodeDstIPPrefix(rule.ip, rule.prefixLen);
        rule.hit = rule.match;

        List<Change> changes = identifyChangesInsert(rule);
        if(already){
            println(changes);
            changeLecTable(changes);
            findRuleToLecIndex(rule);
            if(rule.lecIndex == -1){
                fibs.add(new FIB(rule.hit, rule.forward));
                rule.lecIndex = fibs.size()-1;
            }
            getChangeToUpdateCIB(changes, ts);
        }
    }

    public List<Change> identifyChangesDelete(Rule rule) {
        List<Change> changes = new LinkedList<>();
        BDD bdd = bddEngine.getBDD();
        int hit = rule.getHit();
        rules.remove(rule);
        rules.sort(Comparator.comparingInt(r -> r.prefixLen));
        for (int i=rules.size()-1; i>=0&&hit>0;i--) {
            Rule r1 = rules.get(i);
            if(r1.getPriority() > rule.getPriority()){
                continue;
            }
            int intersection;
            synchronized (bdd){
                intersection = bdd.ref(bdd.and(hit, r1.match));
            }
            if (intersection != 0) {

                if (!rule.forward.equals(r1.forward)) {
                    Change change = new Change(intersection, rule.forward, r1.forward);
                    changes.add(change);
                }
                synchronized (bdd){

                    r1.hit = bdd.orTo(r1.hit, intersection);
                    int tmp = bdd.ref(bdd.not(intersection));
                    hit = bdd.ref(bdd.and(hit, tmp));
                    bdd.deref(tmp);
                    bdd.deref(intersection);
                }
            }
        }
        synchronized (bdd){
            bdd.deref(rule.hit);
            bdd.deref(rule.match);
        }
        toRule.remove(rule);
        return changes;
    }


    List<Change> identifyChangesInsert(Rule rule){
        List<Change> changes = new ArrayList<>();
        BDD bdd = bddEngine.getBDD();
        ArrayList<Rule> rs = addAndGetAllUntil(rule);
        for (Rule r : rs) {
            if (r.getPriority() > rule.getPriority()) {
                int newHit;
                synchronized (bdd){
                    int tmp = bdd.ref(bdd.not(r.getHit()));
                    newHit = bdd.ref(bdd.and(rule.getHit(), tmp));
                    bdd.deref(tmp);
                    bdd.deref(rule.getHit());
                }
                rule.hit = newHit;
            }

            if (r.getPriority() < rule.getPriority()) {
                int intersection;
                synchronized (bdd){
                    intersection = bdd.ref(bdd.and(r.getHit(), rule.getHit()));
                }
                if (intersection == 0) continue;
                if (!r.forward.equals(rule.forward)) {
                    changes.add(new Change(intersection, r.forward, rule.forward));
                    synchronized (bdd){
                        bdd.ref(intersection);
                    }
                }
                int newHit;
                synchronized (bdd){
                    int tmp = bdd.ref(bdd.not(intersection));
                    newHit = bdd.ref(bdd.and(r.getHit(), tmp));
                    bdd.deref(tmp);
                    bdd.deref(intersection);
                    bdd.deref(r.getHit());
                }

                // this.model.ruleHitUpdate(r.getHit(), newHit, r.getPort());
                r.hit=newHit;
            }
        }
        toRule.put(rule, rule);
        return changes;
    }

    protected void getChangeToUpdateCIB(List<Change> changes, long t) {

        for (Node[] ns : nodes.values())
            for (Node n : ns) {
                String id = Utility.getRandomString();
                addRecord(id, n.index, "* cib change start", t);
                long ts = System.nanoTime();
//                addRecord(id, n.index, "lec change", ts-t);
                n.getChangeToUpdateCIB(changes, id);
            }
    }

    private ArrayList<Rule> addAndGetAllUntil(Rule rule) {
        ArrayList<Rule> rules = trie.addAndGetAllOverlappingWith(rule);
//        System.out.println(rule.ip);
        return rules;
    }

    protected boolean fileOpen(String filePath){
        try {
            File file = new File(CONFIG_DIR_PATH + filePath);
            if (file.isFile() && file.exists()) {
                isr = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);
                br = new BufferedReader(isr);
                return true;
            }else{
                System.err.printf("%s配置文件不存在\n", filePath);
                return false;
            }
        }catch (IOException e){
            e.printStackTrace();
            return false;
        }
    }

    protected void fileClose(){
        try {
            if(br != null) br.close();
            if(isr != null) isr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    protected String readFileLine(){
        try {
            return br.readLine();
        } catch (IOException e) {
            return null;
        }
    }

    protected String[] split(String s, int num){
        return s.split("\\s+", num);
    }

    private void linkAll(){
        Collection<PortInterface> ps = ports.values();
        for(PortInterface p:ps){
            p.testLink();
        }
    }

    public void status(){
        BDD bdd = bddEngine.getBDD();
        int i=0;
        for(FIB fib:fibs){
            printf("%s-predicate(%s):\n ", i, fib.predicate);
            printf("%s-forward action:%s %s\n",  i++, fib.type, fib.forward);
        }
    }

    public void doCommand(String param){
        doCommand(param.split("\\s+"));
    }

    public void doCommand(String[] params){
        if(params.length == 0) return;

        switch (params[0]) {
            case "send":{
                if(params.length < 3) break;
                send(params[1], new Message(Message.MessageType.Message, params[2]));
                return;
            }
            case "write":{
                if(logRecord)
                    lr.writeAll();
                return;
            }
            case "close":{
                close();
                return;
            }
            case "count": {
                if(params.length < 2) break;
                int reqIndex = Integer.parseInt(params[1]);

                for (Node n : getNodeList(reqIndex)) {
                    n.startCount();
                }
//                Utility.writeFileLastLine(timeFilePath, String.format("start %s\n", t));

                return;
            }
            case "link": {
                linkAll();
                return;
            }
            case "fwd": {
                if(params.length < 2) break;
                readForwardFileRuntime(params[1]);
                return;
            }
            case "fib": {
                if(params.length < 3) break;
                switch (params[1]){
                    case "change":{
                        int index = Integer.parseInt(params[2]);
                        String[] args = new String[params.length-3];
                        System.arraycopy(params, 3, args, 0, args.length);
                        changeFib(index, args);
                        break;
                    }
                    case "add": {
                        String[] args = new String[params.length-2];
                        System.arraycopy(params, 2, args, 0, args.length);
                        addFib(args);
                        break;
                    }
                    case "del": {
                        int index = Integer.parseInt(params[2]);
                        removeFib(index);
                        break;
                    }
                    case "read": {
                        readFibFile(params[2]);
                        break;
                    }
                }
                return;
            }
            case "status": {
                status();
                return;
            }
            case "req":{
                showRequirentmentList();
                return;
            }
            case "pre": {
                if(params.length < 2) break;
                printPredicate(Integer.parseInt(params[1]));
                return;
            }
            case "preand": {
                if(params.length < 3) break;
                BDD bdd = bddEngine.getBDD();
                synchronized (bdd){
                    int t = bdd.ref(bdd.and(Integer.parseInt(params[1]), Integer.parseInt(params[2])));
                    println(t);
                    bdd.deref(t);
                }
                return;
            }
            case "preor": {
                if(params.length < 3) break;
                BDD bdd = bddEngine.getBDD();
                synchronized (bdd){
                    int t = bdd.ref(bdd.or(Integer.parseInt(params[1]), Integer.parseInt(params[2])));
                    println(t);
                    bdd.deref(t);
                }
                return;
            }
            case "prenot": {
                if(params.length < 2) break;
                BDD bdd = bddEngine.getBDD();
                int t = bdd.ref(bdd.not(Integer.parseInt(params[1])));
                println(t);
                bdd.deref(t);
                return;
            }
            case "insert": {
                if(params.length<4) break;
                insertRule(Long.parseLong(params[1]), Integer.parseInt(params[2]), params[3]);
                return;
            }
            case "delete": {
                if(params.length<4) break;
                deleteRule(Long.parseLong(params[1]), Integer.parseInt(params[2]), params[3]);
                return;
            }
            case "modify": {
                if(params.length<5) break;
                modifyRule(Long.parseLong(params[1]), Integer.parseInt(params[2]), params[3], params[4]);
                return;
            }
            case "":{
                return;
            }

        }
        println("参数错误");
    }

    void setRequirementPacketSpace(){
        int p = 0;
        BDD bdd=bddEngine.getBDD();
        for(FIB fib:fibs){
            if(!ports.containsKey(fib.forward.iterator().next()));
            p = bdd.orTo(p, fib.predicate);
        }
        requirementMap.get(0).packetSpace = p;
    }

    private void showRequirentmentList(){
        for(Integer index:requirementMap.keySet()){
            Requirement r = requirementMap.get(index);
            printf("%d: %s\n", index, r.packetSpace);
        }
    }
    public void removeFib(int index){
        Vector<FIB> oldFibs = new Vector<>(fibs);
        fibs.remove(index);
        updateFibForNode(oldFibs);
    }

    public void addFib(String[] args){
        FIB.ForwardType type;
        try {
            type = FIB.ForwardType.valueOf(args[0]);
        }catch (IllegalArgumentException e){
            System.err.println("fib type error.");
            return;
        }
        int p = Integer.parseInt(args[1]);
        if(!bddEngine.getBDD().isValid(p)) {
            System.err.println("predicate(bdd index) is invalid.");
            return;
        }

        HashSet<String> ports = new HashSet<>(Arrays.asList(args).subList(2, args.length));

        FIB fib = new FIB(type, ports, p);

        Vector<FIB> oldFibs = new Vector<>(fibs);
        fibs.add(fib);
        updateFibForNode(oldFibs);
    }

    public void changeFib(int index, String... args){
        if(fibs.size() < index) {
            System.err.println("fib index error.");
            return;
        }
        FIB oldFib = fibs.get(index);
        FIB fib = new FIB(oldFib);
        int pointer = 1;
        boolean allChange = false;
        switch (args[0]){
            case "all":{
                allChange = true;
            }
            case "type":{
                try {
                    fib.type = FIB.ForwardType.valueOf(args[pointer++]);
                }catch (IllegalArgumentException e){
                    System.err.println("fib type error.");
                    return;
                }
                if (!allChange) break;
            }
            case "predicate":{
                int p = Integer.parseInt(args[pointer++]);
                if(bddEngine.getBDD().isValid(p)){
                    fib.predicate = p;
                }else{
                    System.err.println("predicate(bdd index) is invalid.");
                    return;
                }
                if (!allChange) break;
            }
            case "port":{
                fib.forward = new HashSet<>();
                if(args.length > pointer)
                    fib.forward.addAll(Arrays.asList(args).subList(pointer, args.length));
                break;
            }
            default:{
                System.err.println("args error.");
                return;
            }
        }

        Vector<FIB> oldFibs = new Vector<>(fibs);
        fibs.set(index, fib);
        updateFibForNode(oldFibs);

    }

    public void updateFibForNode(Vector<FIB> oldFibs){
        String id = Utility.getRandomString();
        for(Node[] ns:nodes.values())
            for(Node n:ns){
                n.receiveCount(oldFibs, id);
            }
    }

    public void printPredicate(int pre){
        bddEngine.getBDD().printSet(pre);
    }

    public void close(){
        for(PortInterface p: ports.values()){
            p.close();
        }

        for(Node[] ns:nodes.values())
            for(Node n:ns){
                n.close();
            }
        lr.close();
    }

    public boolean isAlready(){
        for(PortInterface p: ports.values()){
            if(!p.isAlready()) return false;
        }
        return true;
    }

    public void readFibFile(String filename){
        if(!fileOpen(filename))return;
        String id = Utility.getRandomString();
        long ts = System.nanoTime();
        String line;
        Vector<FIB> newFibs = new Vector<>();
        while ((line = readFileLine()) != null){
            String[] data = line.split("\\s+", 4);
            if(data.length != 4) {println("格式错误"); return;}
            FIB.ForwardType _t = data[2].equals("ANY")? FIB.ForwardType.ANY: FIB.ForwardType.ALL;
            newFibs.add(new FIB(_t, data[3].split("\\s+"), Long.parseLong(data[0]), Integer.parseInt(data[1])));
        }

        fileClose();

        if(newFibs.size() == 0) {
            newFibs.add(new FIB(FIB.ForwardType.ALL, new HashSet<>(), 0, 0));
        }

        FIB.SortAndSetPredicate(newFibs, bddEngine);
        mergeFibs();
        for(FIB fib:fibs){
            bddEngine.getBDD().deref(fib.predicate);
        }

        Vector<FIB> oldFibs = fibs;
        fibs = newFibs;
        updateFibForNode(oldFibs);
        addRecord(id, 0, "*fib change start", ts);
    }

    public void run(){
        Thread.currentThread().setName("Device:"+name);
        Scanner sc = new Scanner(System.in);
        while (sc.hasNextLine()) {
            String l = sc.nextLine();
            doCommand(l);
        }
    }

    public static void main(String[] args) {
        if (args.length < 2){
            System.out.println("未输入配置文件名!");
            System.exit(0);
        }
        String f = args.length > 2?args[2]:null;
        Device d1 = new Device(args[0], args[1], f);
        d1.start();
    }

    // for debug
    public HashMap<Integer,Vector<Integer>> getResult(){
        return getNodeList(0)[0].res;
    }

    public void setLogRecord(boolean isOpen){
        logRecord = isOpen;
    }

    public void addRecord(String...s){
        if(logRecord )
            lr.addRecord(s);
    }

    public void addRecord(String id, int index, String action, long time){
        addRecord(id, String.valueOf(index), action, String.valueOf(time));
    }

    public void addRecord(String id, int index, String action, double time){
        addRecord(id, String.valueOf(index), action, String.valueOf(time));
    }

    protected static class LogRecorder extends Thread{
        FileOutputStream fos;
        OutputStreamWriter osw;

        BlockingQueue<String[]> queue;

        Thread th;

        public void close(){
            if(th != null) th.interrupt();
        }

        public LogRecorder(String filename){
            fos = null;
            File f = new File(filename);
            try {
                if (!f.exists()|| !f.isFile()){
                    f.createNewFile();
                }
                fos = new FileOutputStream(filename, true);
                osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
            } catch (IOException e) {
                e.printStackTrace();
            }

            queue = new LinkedBlockingQueue<>();
        }


        public void addRecord(String... s){
            try {
                queue.put(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


        private void write(String[] sl){
            try {
                for(String s:sl){
                    osw.write(s);
                    osw.write(",");
                }
                osw.write("\r\n");
                osw.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public void run(){
            th = Thread.currentThread();
            while (true){
                try {
                    String[] s = queue.take();
                    write(s);
                } catch (InterruptedException e) {

                    String[] s;
                    while (!queue.isEmpty()){
                        s = queue.poll();
                        write(s);
                    }
                    e.printStackTrace();
                    break;
                }
            }
        }

        public void writeAll(){
            String[] s;
            while (!queue.isEmpty()){
                s = queue.poll();
                write(s);
            }
        }
    }
}

